/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 */

import fse from 'fs-extra';

import { hash } from '@ohos/hvigor-common';
import { FileSet, FsOptions } from '../internal/snapshot/util/file-set.js';
import { TaskInputValue } from '../internal/snapshot/util/task-input-value-entry.js';

import { IncrementalDecoratorType } from './types.js';
import { getType, incrementalDeclaratorLogger as _log } from './util.js';

export interface DecoratorDeclarations {
  inputFiles: FileSet;
  outputFiles: FileSet;
  inputs: Map<string, TaskInputValue>;
  ignoreDecoratorTypes: Set<IncrementalDecoratorType>;
}

type Handler = (propertyKey: string, propertyValue: any, decoratorDeclarations: DecoratorDeclarations, options?: FsOptions) => void;

/**
 * 处理Input装饰器的策略函数
 * @param propertyKey
 * @param propertyValue
 * @param inputs
 */
const inputHandler: Handler = (propertyKey, propertyValue, { inputs }) => {
  const allowTypes = ['number', 'string', 'boolean'];

  // null or undefined -> ''
  const value = propertyValue ?? '';

  // number | string | boolean
  if (allowTypes.includes(typeof value)) {
    inputs.set(propertyKey, value);
    return;
  }

  // string[] | number[] | boolean[]
  if (Array.isArray(value) && value.every((v) => allowTypes.includes(typeof v))) {
    inputs.set(propertyKey, value);
    return;
  }

  // others
  inputs.set(propertyKey, hash(JSON.stringify(value)));
};

/**
 * 处理Inputs装饰器的策略函数
 * @param propertyKey
 * @param propertyValue
 * @param decorations
 */
const inputsHandler: Handler = (propertyKey, propertyValue, decorations) => {
  if (!(propertyValue instanceof Map)) {
    _log.debug(`Decorator @Inputs can only be used for 'Map', but get '${getType(propertyValue)}' from '${propertyKey}'.`);
    return;
  }

  for (const [key, value] of propertyValue) {
    inputHandler(`${propertyKey}-${key}`, value, decorations);
  }
};

/**
 * 处理InputFile装饰器的策略
 * @param propertyKey
 * @param propertyValue
 * @param inputFiles
 * @param options
 */
const inputFileHandler: Handler = (propertyKey, propertyValue, { inputFiles }, options) => {
  if (typeof propertyValue !== 'string') {
    _log.debug(`Decorator @InputFile can only be used for 'String', but get '${getType(propertyValue)}' from '${propertyKey}'.`);
    return;
  }
  if (propertyValue === '' || !fse.existsSync(propertyValue)) {
    _log.debug(`File: '${propertyValue}' from '${propertyKey}' is not exists, just ignore.`);
    return;
  }

  // options在写入快照时，会有赋值操作，需要拷贝一下，防止针对同一个对象进行引用赋值
  inputFiles.addEntry(propertyValue, options ? { ...options } : undefined);
};

/**
 * 处理InputFiles装饰器的策略
 * @param propertyKey
 * @param propertyValue
 * @param decorations
 * @param options
 */
const inputFilesHandler: Handler = (propertyKey, propertyValue, decorations, options) => {
  if (!Array.isArray(propertyValue)) {
    _log.debug(`Decorator @InputFiles can only be used for 'Array', but get '${getType(propertyValue)}' from '${propertyKey}'.`);
    return;
  }

  propertyValue.forEach((value) => {
    inputFileHandler(propertyKey, value, decorations, options);
  });
};

/**
 * 处理OutputFile装饰器的策略
 * @param propertyKey
 * @param propertyValue
 * @param outputFiles
 * @param options
 */
const outputFileHandler: Handler = (propertyKey, propertyValue, { outputFiles }, options) => {
  if (typeof propertyValue !== 'string') {
    _log.debug(`Decorator @OutputFile can only be used for 'String', but get '${getType(propertyValue)}' from '${propertyKey}'.`);
    return;
  }
  if (propertyValue === '') {
    _log.debug(`Decorator @OutputFile get empty string from '${propertyKey}', just ignore.`);
    return;
  }

  // options在写入快照时，会有赋值操作，需要拷贝一下，防止针对同一个对象进行引用赋值
  outputFiles.addEntry(propertyValue, options ? { ...options } : undefined);
};

const outputFilesHandler: Handler = (propertyKey, propertyValue, decorations, options) => {
  if (!Array.isArray(propertyValue)) {
    _log.debug(`Decorator @OutputFiles can only be used for 'Array', but get '${getType(propertyValue)}' from '${propertyKey}'.`);
    return;
  }

  propertyValue.forEach((value) => {
    outputFileHandler(propertyKey, value, decorations, options);
  });
};

/**
 * 处理IgnoreSuperIncremental装饰器的策略函数
 * @param propertyKey
 * @param propertyValue
 * @param excludeDecoratorTypes
 */
const ignoreSuperIncrementalHandler: Handler = (propertyKey, propertyValue, { ignoreDecoratorTypes }) => {
  // IgnoreSuperIncremental装饰器只允许装饰declareInputs|declareInputFiles|declareOutputFiles
  const funName2Types = {
    declareInputs: [IncrementalDecoratorType.Input, IncrementalDecoratorType.Inputs],
    declareInputFiles: [IncrementalDecoratorType.InputFile, IncrementalDecoratorType.InputFiles],
    declareOutputFiles: [IncrementalDecoratorType.OutputFile, IncrementalDecoratorType.OutputFiles]
  };
  const keys = Object.keys(funName2Types);
  if (!keys.includes(propertyKey)) {
    _log.debug(`Decorator @IgnoreSuperIncremental can only be used for '${keys.join(', ')}'`);
    return;
  }
  funName2Types[propertyKey as keyof typeof funName2Types].forEach(decoratorType => {
    ignoreDecoratorTypes.add(decoratorType);
  });
};

/**
 * 在收集装饰器装饰的属性时，一些策略处理
 */
export const Strategies: Record<IncrementalDecoratorType, Handler> = {
  [IncrementalDecoratorType.Input]: inputHandler,

  [IncrementalDecoratorType.Inputs]: inputsHandler,

  [IncrementalDecoratorType.InputFile]: inputFileHandler,

  [IncrementalDecoratorType.InputFiles]: inputFilesHandler,

  [IncrementalDecoratorType.OutputFile]: outputFileHandler,

  [IncrementalDecoratorType.OutputFiles]: outputFilesHandler,

  [IncrementalDecoratorType.IgnoreSuperIncremental]: ignoreSuperIncrementalHandler
};
